/*
 * Copyright (C) 2008 Alessandro Sivieri <alessandro.sivieri@chimera-bellerofonte.eu>.
 *
 *  This file is part of Daex.
 *
 *  Daex is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Foobar is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
 */
package eu.chimera_bellerofonte.tesi.encoding;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

import eu.chimera_bellerofonte.tesi.DaexProperties;
import eu.chimera_bellerofonte.tesi.datatypes.Song;


/**
 * Classe astratta di descrizione di un programma da linea di comando
 * che deve essere lanciato in esecuzione; ne conserva i parametri
 * base e le informazioni essenziali.
 * 
 * @author Sivieri Alessandro
 * @author Vitucci Nicola
 * @version 20/apr/07
 */
public abstract class Program
{
	public static final String mp3="lame";
	public static final String ogg="oggenc";
	public static final String wav="mplayer";
	
	/**
	 * HashMap contenente tutti i parametri caratterizzanti
	 * un programma eseguito in modo automatico dal sistema.
	 */
	protected HashMap<Params, String> result=null;
	/**
	 * Stringa di output per la creazione del file finale
	 * di uscita.
	 */
	protected String outputString=null;
	
	/**
	 * Costruttore;ricerca il programma specificato nel
	 * path del sistema e, se lo trova, ne registra il path
	 * nel file di configurazione.
	 * 
	 * @param program il nome dell'eseguibile da agganciare all'applicativo
	 * @throws ProgramNotFoundException eccezione lanciata se il programma non esiste nel path del sistema
	 */
	protected Program(String program) throws ProgramNotFoundException
	{
		result=new HashMap<Params, String>();
		if(DaexProperties.getProperty("program."+program)==null)
		{
			String path;
			if(System.getProperty("os.name").contains("Windows"))
				path=Program.getWinPath(program);
			else
				path=Program.getUnixPath(program);
			if(path==null)
			{
				throw new ProgramNotFoundException("Impossibile trovare l'eseguibile");
			}
			else
			{
				DaexProperties.addProperty("program."+program, path);
			}
		}
	}
	
	private static String getUnixPath(String p)
	{
		String[] paths=System.getenv("PATH").split(":");
		for(String path: paths)
		{
			File f=new File(path+File.separator+p);
			if(f.exists())
			{
				return f.getAbsolutePath();
			}
		}
		return null;
	}
	
	private static String getWinPath(String p)
	{
		String result="";
		if(mp3.equals( p ))
		{
			result=new File("lame"+File.separator+"lame.exe").getAbsolutePath();
		}
		else if(ogg.equals( p ))
		{
			result=new File("oggenc"+File.separator+"oggenc2.exe").getAbsolutePath();
		}
		else if(wav.equals( p ))
		{
			result=new File("mplayer"+File.separator+"mplayer.exe").getAbsolutePath();
		}
		return result;
	}
	
	public static Program getInstance(String name) throws ProgramNotFoundException
	{
		Program p=null;
		if(mp3.equals( name ))
		{
			p=new Lame();
		}
		else if(ogg.equals( name ))
		{
			p=new Oggenc();
		}
		else if(wav.equals( name ))
		{
			p=new MPlayer();
		}
		else
		{
			throw new ProgramNotFoundException("Impossibile trovare la classe");
		}
		return p;
	}
	
	/**
	 * Metodo che restituisce il nome del programma implementato,
	 * inteso come nome del binario da eseguire.
	 * 
	 * @return il nome del programma
	 */
	public abstract String getProgramName();
	
	/**
	 * Metodo che restituisce un hashmap contenente tutte le opzioni
	 * da utilizzare durante l'encoding.
	 * 
	 * @see java.util.HashMap
	 * @return le opzioni del programma per la codifica, come HashMap
	 */
	public HashMap<Params, String> getProgramParams()
	{
		return result;
	}
	
	/**
	 * Metodo per ottenere un parametro specifico dal programma.
	 * Si tratta di un wrapper verso il metodo di default dell'HashMap
	 * 
	 * @see java.util.HashMap#get(Object)
	 * @param p il parametro da conoscere
	 * @return il valore del parametro per il programma specificato
	 */
	public String getProgramParam(Params p)
	{
		return result.get(p);
	}
	
	/**
	 * Metodo che restituisce il massimo valore di qualita' (dove
	 * il minimo per default e' 0) che l'encoder e' in grado di gestire;
	 * lancia una ProgramQualityException se tale metodo non ha senso.
	 * 
	 * @return un intero che rappresenta il massimo valore di qualita' valido
	 * @throws ProgramQualityException eccezione lanciata se il livello di qualita' non e' contemplato
	 */
	public abstract int getQualityRange() throws ProgramQualityException;
	
	/**
	 * Metodo che restituisce i comandi di avvio del programma data la
	 * traccia di cui farne il ripping.
	 * 
	 * @see java.lang.Runtime#exec(String[])
	 * @param s la canzone da estrarre
	 * @param title il titolo del dvd da cui si sta eseguendo il ripping
	 * @param quality la qualita' di estrazione delle tracce (0 se non utilizzato)
	 * @param input la stringa indicante l'input (null se non utilizzata)
	 * @param outdir la directory dove estrarre le tracce
	 * @return un array contenente i comandi da passare in esecuzione
	 */
	public abstract ArrayList<String> getExecCommand(Song s, int title, int quality, String input, String outdir);
	
	/**
	 * Informazione se il programma implementato crea file audio
	 * di base; se vero abilita i metodi di setting di un file di
	 * output a parte e le eventuali post operazioni.
	 * 
	 * @return true se crea le basi
	 */
	public abstract boolean isCreatingBase();
	
	/**
	 * Setta un nuovo file da utilizzare come output.
	 * Questa operazione e' abilitata solo se isCreatingBase()
	 * ritorna true.
	 * 
	 * @param f il nuovo file di output
	 * @throws UnsupportedOperationException eccezione lanciata se questa operazione non e' utilizzata
	 * @throws IllegalArgumentException eccezione lanciata se il file settato non e' compatibile con il programma
	 */
	public void setOutput(File f) throws UnsupportedOperationException, IllegalArgumentException
	{
		throw new UnsupportedOperationException();
	}
	
	/**
	 * Metodo per effettuare le post operazioni necessarie dopo la codifica
	 * se si e' variato l'output file.
	 * Questo metodo e' utilizzato solo se isCreatingBase() ritorna true.
	 * 
	 * @param input il file in input per eseguire le operazioni in uscita
	 * @throws UnsupportedOperationException eccezione lanciata se questa operazione non e' utilizzata
	 * @throws IOException eccezione lanciata in caso di problemi nelle operazioni qui descritte
	 */
	public String[] postOperations(File input) throws UnsupportedOperationException, IOException
	{
		throw new UnsupportedOperationException();
	}
	
	/**
	 * Questo metodo ritorna il valore attuale di outputString.
	 * 
	 * @return il valore attuale di outputString
	 */
	public String getOutputString()
	{
		return outputString;
	}

	/**
	 * Questo metodo setta outputString con il valore dato.
	 * 
	 * @param outputString il nuovo valore per questo campo
	 */
	public void setOutputString(String outputString)
	{
		this.outputString = outputString;
	}
	
	/**
	 * Classe che descrive i parametri del programma Open Source
	 * MPlayer, che codifica files da ac3/dts a wave.
	 * 
	 * @author Sivieri Alessandro
	 * @author Vitucci Nicola
	 * @version 20/apr/07
	 */
	private static class MPlayer extends Program
	{
		private File newOutputFile=null;

		public MPlayer() throws ProgramNotFoundException
		{
			super(Program.wav);
			result.put(Params.VCODEC_MPLAYER, "-vc");
			result.put(Params.VOUTPUT_MPLAYER, "-vo");
			result.put(Params.AOUTPUT_MPLAYER, "-ao");
			result.put(Params.CHAPTER_MPLAYER, "-chapter");
			result.put(Params.EXTENSION, "wav");
			result.put(Params.QUIET, "-quiet");
			result.put(Params.DEVICE_MPLAYER, "-dvd-device");
		}
		
		/**
		 * @see eu.chimera_bellerofonte.tesi.encoding.Program#getProgramName()
		 */
		public String getProgramName()
		{
			if(System.getProperty("os.name").contains("Windows"))
			{
				return "mplayer"+File.separator+"mplayer.exe";
			}
			else
			{
				return DaexProperties.getProperty("program."+Program.wav);
			}
		}

		/**
		 * @see eu.chimera_bellerofonte.tesi.encoding.Program#getQualityRange()
		 */
		public int getQualityRange() throws ProgramQualityException
		{
			throw new ProgramQualityException();
		}

		/**
		 * @see eu.chimera_bellerofonte.tesi.encoding.Program#getExecCommand(eu.chimera_bellerofonte.tesi.datatypes.Song, int, int, java.lang.String, java.lang.String)
		 */
		public ArrayList<String> getExecCommand(Song s, int title, int quality, String input, String outdir)
		{
			ArrayList<String> params=new ArrayList<String>(0);
			
			params.add(getProgramName());
			params.add(getProgramParam(Params.QUIET));
			params.add(getProgramParam(Params.VCODEC_MPLAYER));
			params.add("null");
			params.add(getProgramParam(Params.VOUTPUT_MPLAYER));
			params.add("null");
			params.add(getProgramParam(Params.AOUTPUT_MPLAYER));
			this.outputString=outdir+File.separator+s.getArtist()+" - "+s.getTitle()+"."+getProgramParam(Params.EXTENSION);
			if(System.getProperty("os.name").contains("Windows"))
				params.add("pcm:file=tmp"+File.separator+newOutputFile.getName());
			else
				params.add("pcm:file="+newOutputFile.getAbsolutePath());
			params.add("dvd://"+title);
			params.add(getProgramParam(Params.CHAPTER_MPLAYER));
			params.add(s.getStartChapter()+"-"+s.getEndChapter());
			params.add(getProgramParam(Params.DEVICE_MPLAYER));
			params.add(input);
			return params;
		}

		/**
		 * @see eu.chimera_bellerofonte.tesi.encoding.Program#isCreatingBase()
		 */
		@Override
		public boolean isCreatingBase()
		{
			return true;
		}

		/**
		 * @see eu.chimera_bellerofonte.tesi.encoding.Program#postOperations()
		 */
		@Override
		public String[] postOperations(File input) throws UnsupportedOperationException, IOException
		{
			String[] op=new String[3];
			if(System.getProperty("os.name").contains("Windows"))
			{
				op[0]="copy";
				op[1]="tmp"+File.separator+input.getName();
				op[2]=outputString;
			}
			else
			{
				op[0]="cp";
				op[1]=input.getAbsolutePath();
				op[2]=outputString;
			}
			return op;
		}

		/**
		 * @see eu.chimera_bellerofonte.tesi.encoding.Program#setOutput(java.io.File)
		 */
		@Override
		public void setOutput(File f) throws UnsupportedOperationException, IllegalArgumentException
		{
			if(f.getAbsolutePath().contains(" "))
			{
				throw new IllegalArgumentException("Il nome temporaneo non deve contenere spazi");
			}
			this.newOutputFile=f;
		}

	}
	
	/**
	 * Classe che descrive i parametri del programma Open Source
	 * Lame, che codifica files da wave ad mp3.
	 * 
	 * @author Sivieri Alessandro
	 * @author Vitucci Nicola
	 * @version 20/apr/07
	 */
	private static class Lame extends Program
	{
		
		public Lame() throws ProgramNotFoundException
		{
			super(Program.mp3);
			result.put(Params.QUALITY, "-V");
			result.put(Params.TITLE_TAG, "--tt");
			result.put(Params.ARTIST_TAG, "--ta");
			result.put(Params.ALBUM_TAG, "--tl");
			result.put(Params.GENRE_TAG, "--tg");
			result.put(Params.NUMBER_TAG, "--tn");
			result.put(Params.EXTENSION, "mp3");
			result.put(Params.QUIET, "--quiet");
		}
		
		/**
		 * @see eu.chimera_bellerofonte.tesi.encoding.Program#getProgramName()
		 */
		public String getProgramName()
		{
			if(System.getProperty("os.name").contains("Windows"))
			{
				return "lame"+File.separator+"lame.exe";
			}
			else
			{
				return DaexProperties.getProperty("program."+Program.mp3);
			}
		}

		/**
		 * @see eu.chimera_bellerofonte.tesi.encoding.Program#getQualityRange()
		 */
		public int getQualityRange() throws ProgramQualityException
		{
			return 9;
		}

		/**
		 * @see eu.chimera_bellerofonte.tesi.encoding.Program#getExecCommand(eu.chimera_bellerofonte.tesi.datatypes.Song, int, int, java.lang.String, java.lang.String)
		 */
		public ArrayList<String> getExecCommand(Song s, int title, int quality, String input, String outdir)
		{
			ArrayList<String> params=new ArrayList<String>(0);
			
			params.add(getProgramName());
			params.add(getProgramParam(Params.QUIET));
			params.add(getProgramParam(Params.QUALITY));
			params.add(""+quality);
			params.add(getProgramParam(Params.ALBUM_TAG));
			params.add(s.getAlbum());
			params.add(getProgramParam(Params.ARTIST_TAG));
			params.add(s.getArtist());
			params.add(getProgramParam(Params.GENRE_TAG));
			params.add(s.getGenre().toString());
			params.add(getProgramParam(Params.TITLE_TAG));
			params.add(s.getTitle());
			params.add(getProgramParam(Params.NUMBER_TAG));
			params.add(""+s.getTableValue());
                        if(System.getProperty("os.name").contains("Windows"))
                        {
                            File f=new File(input);
                            params.add("tmp"+File.separator+f.getName());
                        }
                        else
                            params.add(input);
			params.add(outdir+File.separator+s.getArtist()+" - "+s.getTitle()+"."+getProgramParam(Params.EXTENSION));
			return params;
		}

		/**
		 * @see eu.chimera_bellerofonte.tesi.encoding.Program#isCreatingBase()
		 */
		@Override
		public boolean isCreatingBase()
		{
			return false;
		}

	}
	
	/**
	 * Classe che descrive i parametri del programma Open Source
	 * Oggenc (Vorbis-tools), che codifica files da wave ad ogg.
	 * 
	 * @author Sivieri Alessandro
	 * @author Vitucci Nicola
	 * @version 20/apr/07
	 */
	private static class Oggenc extends Program
	{

		public Oggenc() throws ProgramNotFoundException
		{
			super(Program.ogg);
			result.put(Params.QUALITY, "-q");
			result.put(Params.TITLE_TAG, "-t");
			result.put(Params.ARTIST_TAG, "-a");
			result.put(Params.ALBUM_TAG, "-l");
			result.put(Params.GENRE_TAG, "-G");
			result.put(Params.NUMBER_TAG, "-N");
			result.put(Params.OUTPUT, "-o");
			result.put(Params.EXTENSION, "ogg");
			result.put(Params.QUIET, "--quiet");
		}
		
		/**
		 * @see eu.chimera_bellerofonte.tesi.encoding.Program#getProgramName()
		 */
		public String getProgramName()
		{
			if(System.getProperty("os.name").contains("Windows"))
			{
				return "oggenc"+File.separator+"oggenc2.exe";
			}
			else
			{
				return DaexProperties.getProperty("program."+Program.ogg);
			}
		}

		/**
		 * @see eu.chimera_bellerofonte.tesi.encoding.Program#getQualityRange()
		 */
		public int getQualityRange() throws ProgramQualityException
		{
			return 10;
		}

		/**
		 * @see eu.chimera_bellerofonte.tesi.encoding.Program#getExecCommand(eu.chimera_bellerofonte.tesi.datatypes.Song, int, int, java.lang.String, java.lang.String)
		 */
		public ArrayList<String> getExecCommand(Song s, int title, int quality, String input, String outdir)
		{
			ArrayList<String> params=new ArrayList<String>(0);
			
			params.add(getProgramName());
			params.add(getProgramParam(Params.QUIET));
			params.add(getProgramParam(Params.QUALITY));
			params.add(""+quality);
			params.add(getProgramParam(Params.ALBUM_TAG));
			params.add(s.getAlbum());
			params.add(getProgramParam(Params.ARTIST_TAG));
			params.add(s.getArtist());
			params.add(getProgramParam(Params.GENRE_TAG));
			params.add(s.getGenre().toString());
			params.add(getProgramParam(Params.TITLE_TAG));
			params.add(s.getTitle());
			params.add(getProgramParam(Params.NUMBER_TAG));
			params.add(""+s.getTableValue());
			params.add(getProgramParam(Params.OUTPUT));
			params.add(outdir+File.separator+s.getArtist()+" - "+s.getTitle()+"."+getProgramParam(Params.EXTENSION));
			if(System.getProperty("os.name").contains("Windows"))
                        {
                            File f=new File(input);
                            params.add("tmp"+File.separator+f.getName());
                        }
                        else
                            params.add(input);
			return params;
		}

		/**
		 * @see eu.chimera_bellerofonte.tesi.encoding.Program#isCreatingBase()
		 */
		@Override
		public boolean isCreatingBase()
		{
			return false;
		}

	}

}
